#define JMBC_INSIDE
#include "mlMesh.h"
#include <Math/mlMath.h>
#include <Core/clCore.h>
#include <Material/mlMaterial.h>
#include <stdio.h>
#include <GL/glu.h>

#define mshg(g) mesh->groups[ML_MESH_GROUP_ ## g]
#define mshs(t) sizeof(MLMesh ## t)

// Context

#define STORE ID_SMALL_STORE

static MLMesh *store[STORE];

void
mlMeshCreateContext(void)
{
	idClearStore(MLMesh, STORE, store);
}

// Generation / Deletion

static
void
Init(MLMesh *mesh)
{
}

static
void
Term(MLMesh *mesh)
{
}

void
mlGenMeshes(unsigned int n, unsigned int *meshes)
{
	idGenObjects(MLMesh, STORE, store, Init, n, meshes);
}

void
mlDeleteMeshes(unsigned int n, unsigned int *meshes)
{
	idDeleteObjects(store, Term, n, meshes);
}

// Load / Unload

void
mlMeshLoad(unsigned int id, unsigned int filepath)
{
	FILE *stream;
	char tmp[256];

	//stream = _wfopen(tlStringData(filepath), L"rb");
	wcstombs(tmp, tlStringData(filepath), 256);
	stream = fopen(tmp, "rb");

	if(stream)
	{
		MLMesh *mesh;
		MLMeshHeader header;

		mesh = store[id];

		fread(&header, 1, mshs(Header), stream);

		tlGenGroups(4, mesh->groups);

		tlGroupRead(mshg(VERTICES), header.vertices, mshs(Vertex), stream);
		tlGroupRead(mshg(MATERIALS), header.materials, mshs(Material), stream);
		tlGroupRead(mshg(FACES), header.faces, mshs(Face), stream);
		tlGroupRead(mshg(TAGS), header.tags, mshs(Tag), stream);

		fclose(stream);
	}
	else
		elErrorSet(EL_ERROR_INVALID);
}

void
mlMeshUnload(unsigned int id)
{
	MLMesh *mesh;

	mesh = store[id];

	tlDeleteGroups(4, mesh->groups);
}

void
mlMeshesUnload(unsigned int n, unsigned int *meshes)
{
	unsigned int i;

	for(i = 0; i < n; i++)
		mlMeshUnload(meshes[i]);
}

// Operation

void
mlMeshCompile(unsigned int id, unsigned int filepath)
{
	FILE *stream;
	char tmp[256];

	//stream = _wfopen(tlStringData(filepath), L"r");
	wcstombs(tmp, tlStringData(filepath), 256);
	stream = fopen(tmp, "r");

	if(stream)
	{
		MLMesh *mesh;
		wchar_t buffer[512];
		MLMeshHeader header;
		MLMeshVertex *vertices;
		MLMeshMaterial *materials;
		MLMeshFace *faces;
		MLMeshTag *tags;
		unsigned int i;

		mesh = store[id];

		tlGenGroups(4, mesh->groups);

		fgetws(buffer, 512, stream);
		swscanf(buffer, L"vertices %i", &header.vertices);

		wprintf(L"Vertices: %i\n", header.vertices);

		tlGroupAllocate(mshg(VERTICES), header.vertices, mshs(Vertex));
		vertices = tlGroupArray(mshg(VERTICES));

		for(i = 0; i < header.vertices; i++)
		{
			fgetws(buffer, 512, stream);
			swscanf(buffer, L"coord %f %f %f", &vertices[i].coord[0], &vertices[i].coord[1], &vertices[i].coord[2]);

			fgetws(buffer, 512, stream);
			swscanf(buffer, L"normal %f %f %f", &vertices[i].normal[0], &vertices[i].normal[1], &vertices[i].normal[2]);
		}

		fgetws(buffer, 512, stream);
		swscanf(buffer, L"materials %i", &header.materials);

		wprintf(L"Materials: %i\n", header.materials);

		tlGroupAllocate(mshg(MATERIALS), header.materials, mshs(Material));
		materials = tlGroupArray(mshg(MATERIALS));

		for(i = 0; i < header.materials; i++)
		{
			fgetws(buffer, 512, stream);
			memset(materials[i].name, 0, sizeof(wchar_t) * TL_STRING_DEFAULT_FIXED_SIZE);
			wcsncpy(materials[i].name, buffer + wcslen(L"name "), wcslen(buffer) - wcslen(L"name ") - 1);
			materials[i].name[wcslen(buffer) - wcslen(L"name ") - 1] = L'\0';

			fgetws(buffer, 512, stream);
			swscanf(buffer, L"start %i", &materials[i].start);

			fgetws(buffer, 512, stream);
			swscanf(buffer, L"faces %i", &materials[i].faces);
		}

		fgetws(buffer, 512, stream);
		swscanf(buffer, L"faces %i", &header.faces);

		wprintf(L"Faces: %i\n", header.faces);

		tlGroupAllocate(mshg(FACES), header.faces, mshs(Face));
		faces = tlGroupArray(mshg(FACES));

		for(i = 0; i < header.faces; i++)
		{
			fgetws(buffer, 512, stream);
			swscanf(buffer, L"vertices %i %i %i", &faces[i].vertices[0], &faces[i].vertices[1], &faces[i].vertices[2]);

			fgetws(buffer, 512, stream);
			swscanf(buffer, L"normal %f %f %f", &faces[i].normal[0], &faces[i].normal[1], &faces[i].normal[2]);

			fgetws(buffer, 512, stream);
			swscanf(buffer, L"texcoords %f %f %f %f %f %f", &faces[i].texcoords[0][0], &faces[i].texcoords[0][1], &faces[i].texcoords[1][0], &faces[i].texcoords[1][1], &faces[i].texcoords[2][0], &faces[i].texcoords[2][1]);
		}

		fgetws(buffer, 512, stream);
		swscanf(buffer, L"tags %i", &header.tags);

		wprintf(L"Tags: %i\n", header.tags);

		tlGroupAllocate(mshg(TAGS), header.tags, mshs(Tag));
		tags = tlGroupArray(mshg(TAGS));

		for(i = 0; i < header.tags; i++)
		{
			fgetws(buffer, 512, stream);
			memset(tags[i].name, 0, sizeof(wchar_t) * TL_STRING_DEFAULT_FIXED_SIZE);
			wcsncpy(tags[i].name, buffer + wcslen(L"name "), wcslen(buffer) - wcslen(L"name ") - 1);
			tags[i].name[wcslen(buffer) - wcslen(L"name ") - 1] = L'\0';

			fgetws(buffer, 512, stream);
			swscanf(buffer, L"matrix %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f", &tags[i].matrix[0], &tags[i].matrix[1], &tags[i].matrix[2], &tags[i].matrix[3], &tags[i].matrix[4], &tags[i].matrix[5], &tags[i].matrix[6], &tags[i].matrix[7], &tags[i].matrix[8], &tags[i].matrix[9], &tags[i].matrix[10], &tags[i].matrix[11], &tags[i].matrix[12], &tags[i].matrix[13], &tags[i].matrix[14], &tags[i].matrix[15]);
		}

		fclose(stream);
	}
	else
		elErrorSet(EL_ERROR_INVALID);
}

void
mlMeshDecompile(unsigned int id, unsigned int filepath)
{
	FILE *stream;
	char tmp[256];

	//stream = _wfopen(tlStringData(filepath), L"w");
	wcstombs(tmp, tlStringData(filepath), 256);
	stream = fopen(tmp, "w");

	if(stream)
	{
		MLMesh *mesh;
		MLMeshHeader header;
		MLMeshVertex *vertices;
		MLMeshMaterial *materials;
		MLMeshFace *faces;
		MLMeshTag *tags;
		unsigned int i;

		mesh = store[id];

		header.vertices = tlGroupLength(mshg(VERTICES));
		vertices = tlGroupArray(mshg(VERTICES));

		fwprintf(stream, L"vertices %i\n", header.vertices);

		for(i = 0; i < header.vertices; i++)
		{
			fwprintf(stream, L"coord %f %f %f\n", vertices[i].coord[0], vertices[i].coord[1], vertices[i].coord[2]);

			fwprintf(stream, L"normal %f %f %f\n", vertices[i].normal[0], vertices[i].normal[1], vertices[i].normal[2]);
		}

		header.materials = tlGroupLength(mshg(MATERIALS));
		materials = tlGroupArray(mshg(MATERIALS));

		fwprintf(stream, L"materials %i\n", header.materials);

		for(i = 0; i < header.materials; i++)
		{
			fwprintf(stream, L"name %s\n", materials[i].name);

			fwprintf(stream, L"start %i\n", materials[i].start);

			fwprintf(stream, L"faces %i\n", materials[i].faces);
		}

		header.faces = tlGroupLength(mshg(FACES));
		faces = tlGroupArray(mshg(FACES));

		fwprintf(stream, L"faces %i\n", header.faces);

		for(i = 0; i < header.faces; i++)
		{
			fwprintf(stream, L"vertices %i %i %i\n", faces[i].vertices[0], faces[i].vertices[1], faces[i].vertices[2]);

			fwprintf(stream, L"normal %f %f %f\n", faces[i].normal[0], faces[i].normal[1], faces[i].normal[2]);

			fwprintf(stream, L"texcoords %f %f %f %f %f %f\n", faces[i].texcoords[0][0], faces[i].texcoords[0][1], faces[i].texcoords[1][0], faces[i].texcoords[1][1], faces[i].texcoords[2][0], faces[i].texcoords[2][1]);
		}

		header.tags = tlGroupLength(mshg(TAGS));
		tags = tlGroupArray(mshg(TAGS));

		fwprintf(stream, L"tags %i\n", header.tags);

		for(i = 0; i < header.tags; i++)
		{
			fwprintf(stream, L"name %s\n", tags[i].name);

			fwprintf(stream, L"matrix %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f\n", tags[i].matrix[0], tags[i].matrix[1], tags[i].matrix[2], tags[i].matrix[3], tags[i].matrix[4], tags[i].matrix[5], tags[i].matrix[6], tags[i].matrix[7], tags[i].matrix[8], tags[i].matrix[9], tags[i].matrix[10], tags[i].matrix[11], tags[i].matrix[12], tags[i].matrix[13], tags[i].matrix[14], tags[i].matrix[15]);
		}

		fclose(stream);
	}
	else
		elErrorSet(EL_ERROR_INVALID);
}

unsigned int
mlMeshVertices(unsigned int id)
{
	MLMesh *mesh;

	mesh = store[id];

	return tlGroupLength(mshg(VERTICES));
}

unsigned int
mlMeshMaterials(unsigned int id)
{
	MLMesh *mesh;

	mesh = store[id];

	return tlGroupLength(mshg(MATERIALS));
}

unsigned int
mlMeshMaterialMaterial(unsigned int id, unsigned int material)
{
	MLMesh *mesh;

	mesh = store[id];

	return ((unsigned int *) tlGroupArray(mesh->materials))[material];
}

unsigned int
mlMeshFaces(unsigned int id)
{
	MLMesh *mesh;

	mesh = store[id];

	return tlGroupLength(mshg(FACES));
}

unsigned int
mlMeshTags(unsigned int id)
{
	MLMesh *mesh;

	mesh = store[id];

	return tlGroupLength(mshg(TAGS));
}

unsigned int
mlMeshTag(unsigned int id, unsigned int tagname)
{
	MLMesh *mesh;
	MLMeshTag *tags;
	unsigned int i;

	mesh = store[id];

	tags = tlGroupArray(mshg(TAGS));
	for(i = 0; wcscmp(tags[i].name, tlStringData(tagname)); i++);

	return i;
}

void
mlMeshTagMatrix(unsigned int id, unsigned int tag, float matrix[16])
{
	MLMesh *mesh;
	MLMeshTag *tags;

	mesh = store[id];

	tags = tlGroupArray(mshg(TAGS));

	mlMatrixCopy(tags[tag].matrix, matrix);
}

float *
mlMeshMatrix(unsigned int id)
{
	MLMesh *mesh;

	mesh = store[id];

	return mesh->matrix;
}

// Disk I/O

void
mlMeshSave(unsigned int id, unsigned int filepath)
{
	FILE *stream;
	char tmp[256];

	//stream = _wfopen(tlStringData(filepath), L"wb");
	wcstombs(tmp, tlStringData(filepath), 256);
	stream = fopen(tmp, "wb");

	if(stream)
	{
		MLMesh *mesh;
		MLMeshHeader header;

		mesh = store[id];

		header.vertices = tlGroupLength(mshg(VERTICES));
		header.materials = tlGroupLength(mshg(MATERIALS));
		header.faces = tlGroupLength(mshg(FACES));
		header.tags = tlGroupLength(mshg(TAGS));

		fwrite(&header, 1, mshs(Header), stream);

		tlGroupWrite(mshg(VERTICES), stream);
		tlGroupWrite(mshg(MATERIALS), stream);
		tlGroupWrite(mshg(FACES), stream);
		tlGroupWrite(mshg(TAGS), stream);

		fclose(stream);
	}
	else
		elErrorSet(EL_ERROR_INVALID);
}

void
mlMeshGenMaterials(unsigned int id, unsigned int path)
{
	MLMesh *mesh;
	MLMeshMaterial *src;
	unsigned int *dst;
	unsigned int strings[5];
	unsigned int elError;
	unsigned int i;

	mesh = store[id];

	src = tlGroupArray(mshg(MATERIALS));
	tlGenGroups(1, &mesh->materials);
	tlGroupAllocate(mesh->materials, tlGroupLength(mshg(MATERIALS)), sizeof(unsigned int));
	dst = tlGroupArray(mesh->materials);
	mlGenMaterials(tlGroupLength(mshg(MATERIALS)), dst);

	tlGenStrings(4, &strings[1]);
	strings[0] = path;

	for(i = 0; i < tlGroupLength(mshg(MATERIALS)); i++)
	{
		tlStringLoad(strings[1], src[i].name);
		tlStringConcatPath(strings[2], 2, strings);
		tlStringLoad(strings[3], L".cmaterial");
		tlStringConcat(strings[4], 2, &strings[2]);
		mlMaterialLoad(dst[i], strings[4]);
		elError = elErrorGet();
		tlStringsUnload(2, &strings[3]);
		if(elError != EL_ERROR_NONE)
		{
			elErrorSet(EL_ERROR_NONE);
			tlStringLoad(strings[3], L".material");
			tlStringConcat(strings[4], 2, &strings[2]);
			mlMaterialCompile(dst[i], strings[4]);
			tlStringsUnload(2, &strings[3]);
		}
		tlStringsUnload(2, &strings[1]);
		mlMaterialGenTextures(dst[i], strings[0]);
	}

	tlDeleteStrings(4, &strings[1]);
}

void
mlMeshDeleteMaterials(unsigned int id)
{
	MLMesh *mesh;
	unsigned int i;
	unsigned int *materials;

	mesh = store[id];

	materials = tlGroupArray(mesh->materials);
	for(i = 0; i < tlGroupLength(mesh->materials); i++)
		mlMaterialDeleteTextures(materials[i]);
	mlMaterialsUnload(tlGroupLength(mesh->materials), tlGroupArray(mesh->materials));
	tlGroupUnload(mesh->materials);
	tlDeleteGroups(1, &mesh->materials);
}

// OpenGL Integration

void
mlMeshDraw(unsigned int id)
{
	MLMesh *mesh;
	MLMeshVertex *vertices;
	MLMeshMaterial *materials;
	MLMeshFace *faces;
	unsigned int *material;
	unsigned int i, j, k;

	mesh = store[id];

	vertices = tlGroupArray(mshg(VERTICES));
	materials = tlGroupArray(mshg(MATERIALS));
	faces = tlGroupArray(mshg(FACES));

	material = tlGroupArray(mesh->materials);

	for(i = 0; i < tlGroupLength(mshg(MATERIALS)); i++)
	{
		mlMaterialBind(material[i]);

		glMatrixMode(GL_TEXTURE);
		glPushMatrix();
		glMultMatrixf(mlMaterialMatrix(material[i]));
		glMatrixMode(GL_MODELVIEW);

		glBegin(GL_TRIANGLES);
		for(j = materials[i].start; j < materials[i].start + materials[i].faces; j++)
		{
			for(k = 0; k < 3; k++)
			{
				glTexCoord2fv(faces[j].texcoords[k]);
				glNormal3fv(vertices[faces[j].vertices[k]].normal);
				glVertex3fv(vertices[faces[j].vertices[k]].coord);
			}
		}
		glEnd();

		glMatrixMode(GL_TEXTURE);
		glPopMatrix();
		glMatrixMode(GL_MODELVIEW);
	}
}

/*
void
mlMeshFrustumDraw(unsigned int id, unsigned int textures, unsigned int frustum)
{
	unsigned int i, j;
	MLMeshVertex *vertices;
	GLuint *gltextures;
	MLMeshMaterial *materials;
	MLMeshFace *faces;
	MLMesh *mesh;
	mesh = store[id];
	vertices = tlGroupArray(mshg(VERTICES));
	gltextures = tlGroupArray(textures);
	materials = tlGroupArray(mshg(MATERIALS));
	faces = tlGroupArray(mshg(FACES));
	for(i = 0; i < tlGroupLength(mshg(FACES)); i++)
	{
		float triangle[9] =
		{
			vertices[faces[i].vertices[0]].coord[0],
			vertices[faces[i].vertices[0]].coord[1],
			vertices[faces[i].vertices[0]].coord[2],
			vertices[faces[i].vertices[1]].coord[0],
			vertices[faces[i].vertices[1]].coord[1],
			vertices[faces[i].vertices[1]].coord[2],
			vertices[faces[i].vertices[2]].coord[0],
			vertices[faces[i].vertices[2]].coord[1],
			vertices[faces[i].vertices[2]].coord[2],
		};
		if(mlFrustumTriangle(frustum, triangle))
		{
			float ambient[4] =
			{
				materials[faces[i].material].ambient[0],
				materials[faces[i].material].ambient[1],
				materials[faces[i].material].ambient[2],
				materials[faces[i].material].alpha
			};
			float specular[4] =
			{
				materials[faces[i].material].specular[0],
				materials[faces[i].material].specular[1],
				materials[faces[i].material].specular[2],
				materials[faces[i].material].alpha
			};
			float diffuse[4] =
			{
				materials[faces[i].material].diffuse[0],
				materials[faces[i].material].diffuse[1],
				materials[faces[i].material].diffuse[2],
				materials[faces[i].material].alpha
			};
			glBindTexture(GL_TEXTURE_2D, gltextures[materials[faces[i].material].texture]);
			glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, ambient);
			glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse);
			glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specular);
			glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, ambient);
			glBegin(GL_TRIANGLES);
			for(j = 0; j < 3; j++)
			{
				glTexCoord2fv(faces[i].texcoords[j]);
				glNormal3fv(vertices[faces[i].vertices[j]].normal);
				glVertex3fv(vertices[faces[i].vertices[j]].coord);
			}
			glEnd();
		}
	}
}
*/
// ODE Integration

void
mlMeshCreateGeom(unsigned int id, dSpaceID space)
{
	MLMesh *mesh;
	MLMeshVertex *vertices;
	MLMeshFace *faces;

	mesh = store[id];

	vertices = tlGroupArray(mshg(VERTICES));
	faces = tlGroupArray(mshg(FACES));

	mesh->trimesh = dGeomTriMeshDataCreate();
	dGeomTriMeshDataBuildSingle1(mesh->trimesh, vertices[0].coord, mshs(Vertex), tlGroupLength(mshg(VERTICES)), faces[0].vertices, tlGroupLength(mshg(FACES)) * 3, mshs(Face), faces[0].normal);
	mesh->geom = dCreateTriMesh(space, mesh->trimesh, NULL, NULL, NULL);
}

void
mlMeshDestroyGeom(unsigned int id)
{
	MLMesh *mesh;

	mesh = store[id];

	dGeomDestroy(mesh->geom);
	dGeomTriMeshDataDestroy(mesh->trimesh);
}
